using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Threading;
using WheelOfCivilization.Map;
using WheelOfCivilization.Map.MapObject.Movable;
using WheelOfCivilization.Logic;
using WheelOfCivilization.Map.MapObject.Unmovable.Nature;

namespace WheelOfCivilization
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private Vector2 drawReference;
        private static Camera camera;
        List<Model> modelList;
        float aspectRatio;
        Texture2D tileTexture;
        public const int tileSize = 10; //use this as much as possible when calculating tile coordinates
        StateManager stateManager;
        bool loadingState = false;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferMultiSampling = true;

            //graphics.IsFullScreen = true;
            stateManager = new StateManager();
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
            this.IsMouseVisible = true;
            modelList = new List<Model>();
            spriteBatch = new SpriteBatch(GraphicsDevice);
            drawReference = Vector2.Zero;
            aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
            Mouse.WindowHandle = Window.Handle; //the mouse coordinates you get are coordinates according to upper left corner of game windows, not screen 
            camera = new Camera((graphics.PreferredBackBufferWidth / 2), (graphics.PreferredBackBufferHeight / 2));
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {

            tileTexture = this.Content.Load<Texture2D>("Textures/TileSets/square2");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState(PlayerIndex.One).GetPressedKeys().Contains(Keys.Escape))
                this.Exit();

            // check for loading
            if (Keyboard.GetState(PlayerIndex.One).GetPressedKeys().Contains(Keys.F1) && !this.loadingState && this.stateManager.StateIndicator == StateStatus.NoState)
            {
                // enter loading logic.
                this.loadingState = true;
                this.stateManager.OnStateChange += delegate(StateStatus status, State state)
                {
                    this.loadingState = false;
                };

                // attempt to load our state.
                this.stateManager.attemptLoadState();
            }

            if (this.loadingState)
            {
                // we are attempting to load our state. So continue until have one loaded.
                // At the moment this code will never be hit. The event should almost fire immediatly.
                return;
            }

            //new Thread(Logic.Control.control).Start(); //logic is invoked from moving objects


            KeyboardState kS = Keyboard.GetState();
            MouseState mS = Mouse.GetState();

            UpdateCamera(ref kS);

            #region Selecting units
            var selectedMoving = selectableMoving.Where(w => w.mo.selected).FirstOrDefault(); //Selectable contains MovingObject and Rectangle, where the object is drawn
            var selectedStatic = selectableStatic.Where(w => w.wo.selected).FirstOrDefault();
            foreach (SelectableMoving s in selectableMoving)
            {
                if (mouseOnSelectable(s, mS))       //if mouse is above MovingObjects rectangle & is left-clicked
                {
                    if (selectedMoving != null)                 //if there's some allready selected MovingObject
                        selectedMoving.mo.selected = false;     //set the selected to unselected
                    s.mo.selected = true;           //select new one
                    break;
                }
                else
                {
                    if (mS.LeftButton == ButtonState.Pressed)   //if click to some position w/o unit
                    {
                        if (selectedMoving != null)
                            selectedMoving.mo.selected = false;             //deselect selected
                    }
                }
            }
            if (selectedMoving != null && mS.RightButton == ButtonState.Pressed) //if user has selected unit
            {
                bool invokeAction = false;
                WorldObject navigateTo = null;
                foreach (SelectableStatic s in selectableStatic)
                {
                    if (mouseOnSelectable(s, mS))   //if he right clicked some selectable
                    {
                        navigateTo = s.wo;
                        invokeAction = s.wo.GetType() != typeof(Ground);
                        break;
                    }
                }
                moveObject(selectedMoving, navigateTo, invokeAction);
                //if (invokeAction)
                //{
                //    Point p = new Point(mS.X + 10, mS.Y);
                //    moveObject(selectedMoving, p, invokeAction);   //if (invokeAction == false) {only moving} else {begin some action at aend of route}
                //}
                //else
                //{
                //    Point p = new Point(mS.X, mS.Y);
                //    moveObject(selectedMoving, p, invokeAction);
                //}
            }
            #endregion

            #region Selecting Units 2 : NOT WORKING YET (and maybe never will)
            //not sure how to do it, especially input parameters of Unproject() method are written very "circumstantially."
                //I'll explore this option later on, but it'll be needed for rotational & zoomable camera
            //if (mS.LeftButton == ButtonState.Pressed)
            //{
            //    Vector3 nearSource = new Vector3((float)mS.X - 5, (float)mS.Y - 5, 0f);
            //    Vector3 farSource = new Vector3((float)mS.X + 5, (float)mS.Y + 5, 1f);

            //    Matrix world = Matrix.CreateTranslation(Vector3.Zero);

            //    Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, Matrix.Identity, Matrix.CreateTranslation((float)camera.Pos.X,
            //        (float)camera.Pos.Y, 0), world);
            //    Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource, Matrix.Identity, Matrix.CreateTranslation((float)camera.Pos.X,
            //    (float)camera.Pos.Y, 0), world);

            //    Vector3 direction = farPoint - nearPoint;
            //    direction.Normalize();
            //    Ray pickRay = new Ray(nearPoint, direction);

            //    List<SelectableMoving> localSelectableMovingCopy = selectableMoving; //because of thread synchronization - if I locked selectableMoving, Draw() method would have to wait
            //    List<SelectableStatic> localSelectableStaticCopy = selectableStatic;
            //    lock (localSelectableMovingCopy)
            //    {
            //        foreach (SelectableMoving sm in localSelectableMovingCopy)
            //        {
            //            if (pickRay.Intersects(new BoundingBox(new Vector3((float)sm.r.X, (float)sm.r.Y, 0), new Vector3((float)sm.r.X + 10, (float)sm.r.Y + 10, 0))) != null)
            //            {
            //                sm.mo.selected = true;
            //                foreach (SelectableMoving sm2 in localSelectableMovingCopy.Where(w => w.mo.selected == true))
            //                    sm2.mo.selected = false;
            //                break;
            //            }
            //        }
            //    }
                //lock (localSelectableStaticCopy)
                //{

                //}
            //}
            #endregion
            base.Update(gameTime);
        }



        private void UpdateCamera(ref KeyboardState kS)
        {
            //directly references the games KeyboardState
            //(saves memory by using the original not making a copy)

            /*moves the camera, but sometimes it fucks up the navigation system,
            as the coordinates afterwards are not set propperly :/
            it is strange, that the error in navigation takes shape only
            while moving the camera in Y direction, moving in X is okey...*/
            //Movement
            if (kS.IsKeyDown(CameraSettings.ControlKeys.MoveUp))
                camera.Move(0, 1);
            if (kS.IsKeyDown(CameraSettings.ControlKeys.MoveDown))
                camera.Move(0, -1);
            if (kS.IsKeyDown(CameraSettings.ControlKeys.MoveRight))
                camera.Move(1, 0);
            if (kS.IsKeyDown(CameraSettings.ControlKeys.MoveLeft))
                camera.Move(-1, 0);

            //Rotation
            if (kS.IsKeyDown(CameraSettings.ControlKeys.RotateClockwise))
                camera.Rotate(Camera.RotationDirection.Clockwise);
            if (kS.IsKeyDown(CameraSettings.ControlKeys.RotateAntiClockwise))
                camera.Rotate(Camera.RotationDirection.AntiClockwise);

            //Zoom
            if (kS.IsKeyDown(CameraSettings.ControlKeys.ZoomOut))
                camera.Zoom(Camera.ZoomDirection.Out);
            if (kS.IsKeyDown(CameraSettings.ControlKeys.ZoomIn))
                camera.Zoom(Camera.ZoomDirection.In);

            //Reset
            if (kS.IsKeyDown(CameraSettings.ControlKeys.ResetCamera))
                camera.Reset();

            //this.Window.Title = "Rotation = " + camera.Rotation + "f / Zoom = " + camera.Zoom + "f / Position = " + camera.Pos.X + "|" + camera.Pos.Y;
        }

        // IS: Is it possible that we can combined these two methods? Maybe use a interface?
        private bool mouseOnSelectable(SelectableStatic s, MouseState mS)
        {
            //if (mS.RightButton == ButtonState.Pressed)
                if (mS.X <= s.r.Right)
                    if (mS.X >= s.r.Left)
                        if (mS.Y >= s.r.Top)
                            if (mS.Y <= s.r.Bottom)
                                return true;
            return false;
        }


        private bool mouseOnSelectable(SelectableMoving s, MouseState mS)
        {
            if (mS.LeftButton == ButtonState.Pressed)
                if (mS.X <= s.r.Right)
                    if (mS.X >= s.r.Left)
                        if (mS.Y >= s.r.Top)
                            if (mS.Y <= s.r.Bottom)
                                return true;
            return false;
        }

        private void moveObject(SelectableMoving selectedMoving, WorldObject navigateTo, bool invokeAction)
        {
            MovementLogic.Move(selectedMoving.mo, navigateTo, invokeAction);
        }

        private void moveObject(SelectableMoving so, Point mS, bool invokeAction)
        {
            Point mapPoint = new Point(mS.X - (graphics.PreferredBackBufferWidth / 2) - (int)drawReference.X,
                                        mS.Y - (graphics.PreferredBackBufferHeight / 2) - (int)drawReference.Y); //dostanu koncovy bod vzledem k map[0, 0] (ne vzhledem k vykreslovani na obrazovku!)
            Point endPoint = new Point(mapPoint.X / 10, mapPoint.Y / 10); //dostanu offset v poctu dilku
            //if enpoint(X,Y) > 9 -> cluster (napravo,dole)
            //else if 0 < endpoint(X,Y) < 9 -> puvodni cluster
            //      else -> cluster (vlevo, nahore)
            Point clusterPoint = new Point(endPoint.X / 10, endPoint.Y / 10);
            if (endPoint.X > 9)
                clusterPoint.X++;
            if (endPoint.Y > 9)
                clusterPoint.Y++;

            if (endPoint.X < 0)
                clusterPoint.X--;
            if (endPoint.Y < 0)
                clusterPoint.Y--;

            endPoint.X = endPoint.X % 10;
            endPoint.Y = endPoint.Y % 10;
            MovementLogic.Move(so.mo, clusterPoint, endPoint, invokeAction);
        }

        #region To Do

        //public static void waitForLogic()
        //{
        //    controlThread.Join();
        //    if (Logic.Control.newClusters != null || Logic.Control.newClusters.Count != 0)
        //    {
        //        redrawNewClusters(Logic.Control.newClusters);
        //    }
        //}

        private static void redrawNewClusters(List<Map.WorldCluster> list)
        {
            throw new NotImplementedException();
        }
        #endregion

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        /// GraphicsDevice.Viewport.Width
        /// 1920*1080

        List<SelectableMoving> selectableMoving = new List<SelectableMoving>();
        List<SelectableStatic> selectableStatic = new List<SelectableStatic>();

        protected override void Draw(GameTime gameTime)
        {
            selectableMoving = new List<SelectableMoving>();
            selectableStatic = new List<SelectableStatic>();
            graphics.GraphicsDevice.Clear(Color.DarkSlateGray);
            graphics.GraphicsDevice.BlendState = BlendState.Opaque;
            graphics.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;



            //List<WorldCluster> currentScreen = getVisibleClusters();

            var localWorldCopy = Program.world.clusterList;
            lock (localWorldCopy)
            {
                foreach (WorldCluster wc in localWorldCopy)
                {
                    for (int i = 0; i < 10; i++)
                    {
                        for (int j = 0; j < 10; j++)
                        {
                            int rX = (10 * i + 100 * wc.X + GraphicsDevice.Viewport.Width / 2 + (int)drawReference.X);
                            int rY = (10 * j + 100 * wc.Y + GraphicsDevice.Viewport.Height / 2 - (int)drawReference.Y);
                            Rectangle r = new Rectangle(rX, rY, 10, 10);
                            var wo = wc.unmovableContentGS[i, j]; //In reality, tiles and Buildings should be drawn seperately
                            selectableStatic.Add(new SelectableStatic(wo, r));
                            bool b = false;
                            MovingObject selected = null;
                            System.Threading.Monitor.Enter(wc);
                            try
                            {
                                foreach (MovingObject m in wc.movableContentGS)
                                {
                                    if (m.pos.X == i && m.pos.Y == j)
                                    {
                                        b = true;
                                        SelectableMoving sm = new SelectableMoving(m, r);
                                        if (!selectableMoving.Contains(sm))
                                        {
                                            selectableMoving.Add(sm);
                                        }
                                        if (m.selected)
                                            selected = m;
                                        break;
                                    }
                                }
                            }
                            finally
                            {
                                System.Threading.Monitor.Exit(wc);
                            }

                            if (wo.visible)
                            {
                                spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null,
                                    camera.getCameraView(GraphicsDevice)); //applies the camera view without effecting the independant drawing of tiles

                                var drawTextures = new Dictionary<Type, Action> {
                                    { typeof(Ground), () => {
                                        if (b)
                                            spriteBatch.Draw(tileTexture, r, selected == null ? Color.LightBlue : Color.DarkBlue);
                                        else
                                            spriteBatch.Draw(tileTexture, r, Color.White);} 
                                    },
                                    { typeof(Wood), () =>  spriteBatch.Draw(tileTexture, r, Color.Green) },
                                    { typeof(Gold), () =>  spriteBatch.Draw(tileTexture, r, Color.Gold) },
                                    { typeof(Stone), () => spriteBatch.Draw(tileTexture, r, Color.Gray) },
                                    { typeof(Center), () => spriteBatch.Draw(tileTexture, r, Color.Red) },
                                };
                                drawTextures[wo.GetType()]();
                                spriteBatch.End();
                            }
                        }
                    }
                }
            }
        }

        private List<WorldCluster> getVisibleClusters()
        {
            int width = GraphicsDevice.Viewport.Width, height = GraphicsDevice.Viewport.Height, offsetX = (int)(drawReference.X % 10), offsetY = (int)(drawReference.Y % 10);

            WorldCluster center = Program.world.clusterList.Where(w =>
                    (w.X == ((int)drawReference.X / 10 - offsetX)) &&
                    (w.Y == ((int)drawReference.Y / 10 - offsetY))).FirstOrDefault();

            int tmpX = width / (2 * 100); //krajni cluster ma souradnice poziceStrdu - ((sirka monitoru/2) / sirka clusteru)
            int tmpY = height / (2 * 100);

            return Program.world.clusterList.Where(w => w.X >= (center.X - tmpX) && w.X <= (center.X + tmpX) && w.Y >= (center.Y - tmpY) && w.Y <= (center.Y - tmpY)).ToList();
        }
    }

    public class SelectableMoving
    {
        public SelectableMoving(MovingObject mo, Rectangle r)
        {
            this.mo = mo; this.r = r;
            this.Clicked += new EventHandler(SelectableMoving_Clicked);
        }

        void SelectableMoving_Clicked(object sender, EventArgs e)
        {
            this.mo.selected = true;
        }
        public MovingObject mo;
        public Rectangle r;
        public event EventHandler Clicked;
    }

    public class SelectableStatic
    {
        public SelectableStatic(WorldObject wo, Rectangle r)
        {
            this.wo = wo; this.r = r;
            this.Clicked += new EventHandler(SelectableStatic_Clicked);
        }

        void SelectableStatic_Clicked(object sender, EventArgs e)
        {
            this.wo.selected = true;
        }
        public WorldObject wo;
        public Rectangle r;
        public event EventHandler Clicked;
    }
}
